package edu.kit.aifb.lidasora.ppmModel;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import edu.kit.aifb.lidasora.ppmModel.transitionCount.TransitionCount;


/**
 * A ModelNode<T> for constructing a PPM model
 * 
 * A ModelNode with parameter <T> to save any object T i the PPM
 * model.
 * 
 * @author Daniel Schuh
 * 
 * @version 1.0
 * @param <T>
 *            The class that should be saved inside the tree
 */
class ModelNode<T> {

	/*
	 * the content of this node
	 */
	private T content;
	/*
	 * the basic counting value for this Node
	 */
	private final TransitionCount tc;

	// the time of creation
	private final AtomicLong timeCreated = new AtomicLong(
			System.currentTimeMillis());

	//the last access
	private AtomicLong timeChanged = new AtomicLong(System.currentTimeMillis());
	/*
	 * the parent node
	 */
	private ModelNode<T> parent;

	/*
	 * save the children internally via ConcurrentHasMap for getting fast and
	 * synchrone access
	 */
	ConcurrentHashMap<T, ModelNode<T>> children;
	
	

	static int nodesCreated = 0;
	/**
	 * Standard constructor
	 * 
	 * Standard constructor. Sets the counting value to 1.
	 * 
	 * @param parent
	 *            the parent node of this node
	 * @param content the content of this node; use null if this node should be root node
	 * @param transitionCount the way the transitions should be counted
	 */
	ModelNode(ModelNode<T> parent, T content, TransitionCount count) {
		//System.out.println("Created new node with " + content.toString());
		this.content = content;
		this.parent = parent;
		this.tc = count.clone();
		tc.reset();
		
		nodesCreated++;
	}

	/**
	 * indicate that this node successfully contributed to a prediction
	 */
	void successfullyPredicted() {
		this.timeChanged = new AtomicLong(System.currentTimeMillis());
		this.tc.truePrediction();
	}

	/**
	 * indicate that this node has been predicted but the prediction was false
	 */
	void unsuccessfullyPredicted() {
		this.timeChanged = new AtomicLong(System.currentTimeMillis());
		this.tc.falsePrediction();
	}

	/**
	 * indicates that this node has been seen
	 */
	void hasBeenSeen() {
		this.timeChanged = new AtomicLong(System.currentTimeMillis());
		this.tc.increment();
	}


	/**
	 * Checks if this node has a child with Content childContent
	 * 
	 * Checks if this node has a child with content childContent; it also
	 * returns false for leaf nodes with no children or if childContent is null.
	 * 
	 * @param childContent
	 *            the content of the child node that has to be checked
	 * @return true if this node has a child node with content childNode; false
	 *         otherwise
	 */
	boolean hasChild(T childContent) {
		//TODO contains does not what it is supposed to do; fix?
		return (children != null && children.containsKey(childContent));
	}

	/**
	 * Adds a child node with Content childContent
	 * 
	 * Adds a new child node with content childContent. Nothing will be added if
	 * childContent is already a child
	 * 
	 * @param childContent
	 *            the content of the child node that has to be added
	 * @return the new childNode
	 */
	ModelNode<T> addChild(T childContent) {

		this.timeChanged = new AtomicLong(System.currentTimeMillis());
		if (children == null) {
			children = new ConcurrentHashMap<T, ModelNode<T>>();
		}
		if (this.children.containsKey(childContent)) {
			//System.out.println("Okay, node " + childContent.toString() + "has been seen " + this.getCount() + " times");
			this.children.get(childContent).hasBeenSeen();
			return this.children.get(childContent);
		}		
		return children.put(childContent, new ModelNode<T>(this,
						childContent, this.tc));
	}
	
	

	/**
	 * Removes a child with Content childContent
	 * 
	 * Removes a child node with content childContent
	 * 
	 * @param childContent
	 *            the content of the child node that has to be removed
	 * @return true if this node has a child node with content childNode; false
	 *         otherwise
	 */
	boolean removeChild(T childContent) {
		this.timeChanged = new AtomicLong(System.currentTimeMillis());
		return (children != null) && (children.remove(childContent) != null);
	}

	/**
	 * Returns the child node belonging to childContent
	 * 
	 * @param childContent the content of the to-be-found-node
	 * @return the child node belonging to childContent
	 */
	ModelNode<T> getChild(T childContent) {
		return this.children.get(childContent);
	}
	
	/**
	 * Returns the number of children
	 * 
	 * Returns the number of children of this node
	 * 
	 * @return the number of children of this node
	 */
	int getNumberOfChildren() {
		return this.children != null? this.children.size():0;
	}

	/*
	 * ************************************************************************************
	 * 
	 * GETTER-METHODS
	 * 
	 * ************************************************************************************
	 * 
	 */



	/**
	 * Returns this node's parent node
	 * 
	 * Returns this node's parent node
	 * 
	 * @return this node's parent node
	 */
	ModelNode<T> getParent() {
		return parent;
	}


	/**
	 * Returns the content of this node
	 * 
	 * Returns the content of this node
	 * 
	 * @return the content of this node
	 */
	T getContent() {
		return content;
	}
	
	/**
	 * returns the collected statistical data of this node
	 * @return the collected statistical data of this node
	 */
	TransitionCount getData() {
		return tc;
	}
	
	@Override
	public String toString(){
		if (content != null)
			return "["+ content.toString() + "]";
		else 
			return "[ ]";
	}
	

	/**
	 * Returns the last time this node was accessed
	 * 
	 * Returns the last time this node was accessed. This can either be via a
	 * prediction or because it has been seen in a chain of events. The value
	 * returned comes from System.currentTimeMillis()
	 * 
	 * @return the last time this node has been accessed
	 */
	long getLastAccessedTime() {
		return this.timeChanged.get();
	}

	/**
	 * Returns the time of creation
	 * 
	 * Returns the time of creation of this node. The long-value is a result of
	 * a System.currentTimeMillis()-call.
	 * 
	 * @return the time of creation
	 */
	long getTimeCreated() {
		return this.timeCreated.get();
	}
}
